/*
 * yauosk - Yet Another Useless Operating System Kernel
 *
 * Copyright (c) 2009-2010 Matteo Cicuttin
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* Keyboard driver. Some ideas taken from FreeBSD (sys/dev/kbd) */

#define _YAUOSK_KBD_DRIVER_

#include <sys/types.h>


#define	KBDBUFLEN		256

#define	STATE_PLAIN		0x00
#define	STATE_SHIFT		0x01
#define	STATE_CTRL		0x02
#define	STATE_ALT		0x04

#define	ESCAPE			0xE0

#define	KEY_CTRL		0x1D
#define KEY_ALT			0x38
#define KEY_LSHIFT		0x2A
#define	KEY_RSHIFT		0x36

#define	KEY_RELEASE(x)	(x & 0x80)
#define	RELEASED_KEY(x)	(x & 0x7F)

typedef enum {
	PLAIN = 0,
	SHIFT,
	CTRL,
	CTRL_SHIFT,
	ALT,
	ALT_SHIFT,
	ALT_CTRL,
	ALT_CTRL_SHIFT
} key_state_t;

#define NUM_STATES 8 /* Number of entries in key_state */
#define NUM_KEYS 0x6c

/* COPY-PASTE from FreeBSD's sys/kbio.h BEGIN */

struct keyent_t {
	uchar          map[NUM_STATES];
	uchar          spcl;
	uchar          flgs;
#define FLAG_LOCK_O     0
#define FLAG_LOCK_C     1
#define FLAG_LOCK_N     2
};
 
struct keymap {
	ushort         n_keys;
	struct keyent_t key[NUM_KEYS];
};
typedef struct keymap keymap_t;

/* COPY-PASTE from FreeBSD's sys/kbio.h END */

#include "kbdmap.h"

static	key_state_t key_state;
static	int escape;
static	int xlate;

/* Keyboard circular buffer */
static	uint buf_start, buf_end;
static	uchar kbdbuf[KBDBUFLEN];

static void kbd_putc(uchar);
static uchar kbd_translate(uchar);
static void kbd_event(uchar);

void
kbd_init(void)
{
	/* initialize circular buffer */
	buf_start = 0;
	buf_end = 0;
	/* enable translation from scancodes to characters */
	xlate = 1;
	/* There is no special key pressed*/
	key_state = PLAIN;
	escape = 0;				/* Got 0xE0 scancode? */
	
	atkbdc_register_callback(&kbd_event);
	printf("kbd: keyboard driver loaded\n");
}

static uint
buffer_full(uint start, uint stop)
{

}

uchar
kbd_getc(void)
{
	char retval;
	/* acquire lock for kbd buffer */
	if (buf_start == KBDBUFLEN)
		buf_start = 0;
	retval = kbdbuf[buf_start];
	buf_start++;
	/* release lock for kbd buffer */
}


/*
 * beware: kbd_putc is triggered by the kbd interrupt
 */

static void
kbd_putc(uchar c)
{
	/* acquire lock for kbd buffer */
	if (buf_end == KBDBUFLEN)
		buf_end = 0;
	kbdbuf[buf_end] = c;
	buf_end++;
	/* release lock for kbd buffer */
}

static uchar
kbd_translate(uchar scancode)
{	
	if (scancode == ESCAPE)
	{
		escape = 1;
		return NOP;
	}
	
	if (KEY_RELEASE(scancode))
	{
		switch(RELEASED_KEY(scancode))
		{
			case KEY_CTRL:
				key_state &= (~STATE_CTRL);
				escape = 0;
				break;
				
			case KEY_ALT:
				key_state &= (~STATE_ALT);
				escape = 0;
				break;
				
			case KEY_LSHIFT:
			case KEY_RSHIFT:
				key_state &= (~STATE_SHIFT);
				escape = 0;
				break;
				
			default:
				escape = 0;
				break;
		}
		return NOP;
	}
	else
	{
		switch (scancode)
		{
			case KEY_CTRL:
				key_state |= STATE_CTRL;
				break;
				
			case KEY_ALT:
				key_state |= STATE_ALT;
				break;
				
			case KEY_LSHIFT:
			case KEY_RSHIFT:
				key_state |= STATE_SHIFT;
				break;
				
			default:
				if (usa_key_map.key[scancode].spcl == 0xFF || scancode > NUM_KEYS)
					return NOP;
				else
					return usa_key_map.key[scancode].map[key_state];
		}
	}
}

static void
kbd_event(uchar scancode)
{
	char tc;
	
	if (xlate)
	{
		/*
		 * Translation must be done outside the interrupt handler, move
		 * this elsewhere
		 */
		if( (tc = kbd_translate(scancode)) != NOP )
			kbd_putc( tc ); 
	}
	else
		kbd_putc(scancode);
}

